//
// Created by redcat1 on 01.05.2024.
//

#ifndef TASK_ARRAY_VECTOR__H
#define TASK_ARRAY_VECTOR__H

#include <string>
#include <sstream>
#include <iomanip>
#include "vector_exception.h" // for VectorException

template <class T, int BufferSize=-1>
class Vector
{
public:
  Vector(int size_, const T& value_);
  Vector(const Vector &vector);
  Vector& operator=(const Vector &vector);
  ~Vector() = default;

  const T& operator[](int index) const; // возвращает элемент по индексу
  T& operator[](int index); // возвращает элемент по индексу
  int Size() const; // возвращает размер массива
  T Erase(int index); // удаляет элемент по индексу
  void Insert(int index, const T& value); // вставляет элемент по индексу

private:
  void CheckIndex(int index, int min, int max) const; // проверка корректности индекса

private:
  T data[BufferSize];
  int size;
};


//
template <class T, int BufferSize>
inline Vector<T, BufferSize>::Vector(int size_, const T& value_)
  : size(size_)
  , data()
{
  if (size_ < 0)
    throw VectorNegativeSize(size_);

  if (size_ > BufferSize)
    throw VectorBiggerThanCapacity(size_, BufferSize);

  for (int i = 0; i < size_; i++)
  {
    data[i] = value_;
  }
}

//
template <class T, int BufferSize>
inline Vector<T, BufferSize>::Vector(const Vector &vector)
  : size(vector.size)
  , data()
{
  for (int i = 0; i < size; i++)
  {
    data[i] = vector.data[i];
  }
}

//
template <class T, int BufferSize>
inline Vector<T, BufferSize>& Vector<T, BufferSize>::operator=(const Vector &vector)
{
  if (this != &vector)
  {
    size = vector.size;
    for (int i = 0; i < size; i++)
    {
      data[i] = vector.data[i];
    }
  }
  return *this;
}

//
template <class T, int BufferSize>
inline const T& Vector<T, BufferSize>::operator[](int index) const
{
  CheckIndex(index, 0, size - 1);
  return data[index];
}

//
template <class T, int BufferSize>
inline T& Vector<T, BufferSize>::operator[](int index)
{
  CheckIndex(index, 0, size - 1);
  return data[index];
}

//
template <class T, int BufferSize>
inline int Vector<T, BufferSize>::Size() const
{
  return size;
}

//
template <class T, int BufferSize>
T Vector<T, BufferSize>::Erase(int index_)
{
  CheckIndex(index_, 0, size-1);
  T value = data[index_];
  for (int i = index_; i < size-1; ++i)
  {
    std::swap(data[i], data[i+1]);
  }
  --size;
  return value;
}

//
template <class T, int BufferSize>
void Vector<T, BufferSize>::Insert(int index_, const T& value_)
{
  CheckIndex(index_, 0, size);
  data[size] = value_;
  ++size;
  for (int i = size-1; i > index_; --i)
  {
    std::swap(data[i], data[i-1]);
  }
}

//
template <class T, int BufferSize>
void Vector<T, BufferSize>::CheckIndex(int index, int min, int max) const
{
  if (index < 0)
    throw VectorNegativeIndex(index);
  if (index < min || index > max)
    throw VectorIndexOutOfRange(index, max-min+1);
}

//
template <class T>
class Vector<T, -1>
{
public: // конструкторы
  Vector(int size_, const T& value_);
  Vector(const Vector &vector);
  Vector& operator=(const Vector &vector);
  ~Vector();

  const T& operator[](int index) const; // возвращает элемент по индексу
  T& operator[](int index); // возвращает элемент по индексу
  int Size() const { return size;}; // возвращает размер массива
  T Erase(int index); // удаляет элемент по индексу
  void Insert(int index, const T& value); // вставляет элемент по индексу

private:
  void Reserve(int capacity_); // увеличить размер массива
  void CheckIndex(int index, int min, int max) const; // проверка корректности индекса

private:
  T * data; // массив, который будет хранить элементы
  int size; // размер массива
  int capacity; // максимальное количество элементов в массиве
};

//
template <class T>
inline Vector<T, -1>::Vector(int size_, const T& value_)
  : data(nullptr)
  , size(size_)
  , capacity(2*size_)
{
  if (size_ < 0)
    throw VectorNegativeSize(size_);

  if (capacity > 0)
  {
    data = new T[capacity];
    for (int i = 0; i < size; ++i)
    {
      data[i] = value_;
    }
  }
}

//
template <class T>
inline Vector<T, -1>::Vector(const Vector& v)
  : data(nullptr)
  , size(v.size)
  , capacity(v.capacity)
{
  if (capacity > 0)
  {
    data = new T[capacity];
    for ( int i = 0; i < size; i++ )
    {
      data[i] = v.data[i];
    }
  }
}

//
template <class T>
inline Vector<T, -1>& Vector<T, -1>::operator=(const Vector& v)
{
  if (this != &v)
  {
    size = v.size;
    capacity = v.capacity;
    delete [] data;
    data = nullptr; // если массив пустой, то указатель data будет равен nullptr
    if (capacity > 0)
    {
      data = new T[capacity];
      for (int i = 0; i < size; i++)
      {
        data[i] = v.data[i];
      }
    }
  }
  return *this;
}

//
template <class T>
inline Vector<T, -1>::~Vector()
{
  delete [] data;
}

//
template <class T>
inline const T& Vector<T, -1>::operator[](int index) const
{
  CheckIndex(index, 0, size-1);
  return data[index];
}

//
template <class T>
inline T& Vector<T, -1>::operator[](int index)
{
  CheckIndex(index, 0, size-1);
  return data[index];
}

//
template <class T>
inline T Vector<T, -1>::Erase(int index)
{
  CheckIndex(index, 0, size-1);
  T value = data[index];
  for (int i = index; i < size-1; ++i)
  {
    std::swap(data[i], data[i+1]);
  }
  --size;
  return value;
}

//
template <class T>
inline void Vector<T, -1>::Insert(int index, const T& value)
{
  CheckIndex(index, 0, size);
  if (size == capacity)
    Reserve(2*size);

  data[size] = value;
  ++size;
  for (int i = size-1; i > index; --i)
  {
    std::swap(data[i], data[i-1]);
  }
}

//
template <class T>
inline void Vector<T, -1>::Reserve(int capacity_)
{
  if (capacity_ < 0)
    throw VectorNegativeSize(capacity_);

  if (capacity_ > capacity)
  {
    capacity = capacity_;
    T* new_data = new T[capacity];
    for ( int i = 0; i < size; i++ )
    {
      new_data[i] = data[i];
    }
    delete [] data;
    data = new_data;
  }
}

//
template <class T>
inline void Vector<T, -1>::CheckIndex( int index, int min, int max ) const
{
  if (index < 0)
    throw VectorNegativeIndex(index);
  if ( index < min || index > max )
    throw VectorIndexOutOfRange(index, max-min+1);
}

//
template <class VectorT>
inline std::string ToString(const VectorT& v)
{
  std::stringstream ss;
  ss << std::setprecision(4) << "{";
  for (int i = 0; i < v.Size(); ++i)
  {
    ss << v[i];
    if (i != v.Size() - 1)
    {
      ss << ", ";
    }
  }
  ss << "}";
  return ss.str();
}

#endif //TASK_ARRAY_VECTOR__H
